import { newArrayProto } from "./array";
import Dep from "./dep";

// 开始数据劫持
class Observer {
  constructor(data) {
    // 给每个对象额外增加个dep收集watcher
    // 为了实现能让数组更新/对象新增属性也更新视图,方便实现$set
    this.dep = new Dep();
    // Object.defineProperty 只能劫持已经存在的属性,Vue会单独写一些api解决这个问题($set,$detele)
    // 把这个Observer实例暴露出去,方便外面的调,如果对象身上有__ob__属性,就说明这个对象被观测过
    Object.defineProperty(data, '__ob__', {
      value: this,
      // 要让他不可枚举,不然后面walk会访问到这个属性,就死循环了
      enumerable: false
    });
    if (Array.isArray(data)) {
      // 我们一般修改数组时很少写修改索引的代码(arr[9999]=1)
      // 如果对9999条数据做劫持会非常浪费性能,而且push等数组方法劫持不到
      // 所以只需重写7个改变原数组的方法对数据劫持就行了
      data.__proto__ = newArrayProto;
      // 劫持数组中的对象
      this.observeArray(data);
    } else {
      this.walk(data);
    }
  }
  // 循环对象对属性依次劫持
  walk(data) {
    // "重新定义"属性
    Object.keys(data).forEach(key => defineReactive(data, key, data[key]));
  };
  // 劫持数组中的对象,代理数组中有可能item是对象,要对此对象劫持 data.arr[3].a = 3 是合法的
  observeArray(data) {
    data.forEach(item => observe(item));
  }
}

// 让数组中的数组也用dep收集watcher
function dependArray(value) {
  value.forEach(item => {
    item.__ob__ && item.__ob__.dep.depend();
    if (Array.isArray(item)) {
      dependArray(item);
    }
  })
}
// 定义响应式数据
export function defineReactive(target, key, value) {
  // 递归对value再次劫持,childOb.dep用来收集依赖的
  const childOb = observe(value);
  // 为每个属性添加dep
  const dep = new Dep();
  Object.defineProperty(target, key, {
    get() {
      // 如果实例watcher时关联dep了
      if (Dep.target) {
        // 让这个属性的收集器记住当前的watcher
        dep.depend();
        if (childOb) {
          // 如果dep有值,就让dep也收集一次依赖
          childOb.dep.depend();
          if (Array.isArray(value)) {
            // 让数组中的数组也用dep收集watcher,深沉的数组改了之后也能通知watcher更新视图
            dependArray(value);
          }
        }
      }
      return value;
    },
    set(newValue) {
      if (value === newValue) return;
      // 赋值要再次代理一次,有可能赋的值也是对象(data.obj={a:1})
      observe(newValue);
      value = newValue;
      // 让对应的dep去通知视图更新
      dep.notify();
    }
  })
}

// 数据劫持
export function observe(data) {
  if (typeof data !== 'object' || data === null) {
    // 只对对象劫持
    return;
  }
  // 如果这个data已经被代理过了,就直接返回代理后的数据就行了
  if (data.__ob__ instanceof Observer) {
    return data.__ob__;
  }
  // 开始数据劫持
  // 如果数据被劫持过就不要再被劫持了
  // 要判断一个对象是否被劫持过,可以添加一个实例,用实例来判断是否被劫持过
  return new Observer(data);
}